﻿using StarryEdge.Core;
using StarryEdge.Core.Comparer;
using StarryEdge.Core.Manager;
using StarryEdge.Core.Models;
using StarryEdge.Core.Plugin;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace StarryEdge.Plugin
{
    public class PluginManager : IPluginManager
    {
        private List<IPlugin> _plugins = new List<IPlugin>();
        private string _filePath = Path.Combine(Environment.CurrentDirectory, "Update");
        private string _pluginPath = Path.Combine(Environment.CurrentDirectory, "Plugin");
        private string _pluginBackup = Path.Combine(Environment.CurrentDirectory, "Backup");
        private string _pluginPluginCache = Path.Combine(Environment.CurrentDirectory, "PluginCache");

        private ICompressionDecompression _compression;
        private IStarryEdgeLog _starryEdgeLog;
        private IMenuUpdate _menuUpdate;
        private readonly object _lock = new object();
        public PluginManager(ICompressionDecompression compression, IStarryEdgeLog starryEdgeLog, IMenuUpdate menuUpdate)
        {
            _starryEdgeLog = starryEdgeLog;
            _compression = compression;
            _menuUpdate = menuUpdate;

            ThreadPool.QueueUserWorkItem(new WaitCallback(x => {
                while (true)
                {
                    lock (_lock)
                    {
                        PackageMonitor();
                        DetectionRule();
                        Thread.Sleep(400);                        
                    }
                }
            }));
        }

        public List<IPlugin> GetPlugins(Func<IPlugin, bool> action) => _plugins.Where(action).ToList();

        public List<PluginInfoModel> GetPluginInfos() => _plugins.Select(x => x.PluginInfo).ToList();

        private void PackageMonitor()
        {
            if (!Directory.Exists(_pluginBackup)) { Directory.CreateDirectory(_pluginBackup); }
            if (!Directory.Exists(_filePath)) { Directory.CreateDirectory(_filePath); }
            if (!Directory.Exists(_pluginPluginCache)) { Directory.CreateDirectory(_pluginPluginCache); }
            DirectoryInfo directoryInfo = new DirectoryInfo(_filePath);
            foreach (FileInfo fileInfo in directoryInfo.GetFiles())
            { 
                if (fileInfo.Extension == ".7z")
                {
                    _compression.Decompression(fileInfo.FullName, _pluginPluginCache);
                }
                File.Delete(fileInfo.FullName);
            }
        }

        private void DetectionRule()
        {
            DirectoryInfo directoryInfo = new DirectoryInfo(_pluginPluginCache);
            var files = directoryInfo.GetFiles();
            FileNameComparer fileNameComparer = new FileNameComparer();
            Array.Sort(files, fileNameComparer);
            foreach (FileInfo fileInfo in files)
            {
                if (fileInfo.Extension == ".dll" && fileInfo.FullName.Contains("StarryEdge.Module"))
                {
                    IPlugin plugin = new Plugin(new PluginInfoModel("", 1, 1, "1", "test", "test", "", ""));
                    Assembly assembly = plugin.LoadPlugin(_starryEdgeLog, fileInfo.FullName);
                    Type type = assembly.GetTypes().FirstOrDefault(t => t.IsClass && typeof(StarryEdge.Core.Models.PluginInfoModel).IsAssignableFrom(t));
                    if (type != null)
                    {
                        PluginInfoModel pluginInfo = Activator.CreateInstance(type) as StarryEdge.Core.Models.PluginInfoModel;
                        if (pluginInfo != null)
                        {
                            string barckupFile = Path.Combine(_pluginPath, Path.GetFileName(fileInfo.FullName));
                            IPlugin plugin1 = new Plugin(new PluginInfoModel(pluginInfo.Name, pluginInfo.Id, pluginInfo.Type, pluginInfo.Version, pluginInfo.Author, pluginInfo.Description, barckupFile, pluginInfo.DllName));
                            plugin.UnLoadPlugin(_starryEdgeLog);
                            var pluginModel = _plugins.FirstOrDefault(x => x.PluginInfo.Id == pluginInfo.Id && x.PluginInfo.Type == pluginInfo.Type);
                            if (plugin1.PluginInfo.Type > 3)
                            {
                                _starryEdgeLog.LogRun("Plugins that do not implement this type", Core.Enums.LogLevel.Error);
                                try
                                {
                                    File.Delete(fileInfo.FullName);
                                }
                                catch (Exception ex)
                                {
                                    _starryEdgeLog.LogRun($"Delete Plugin Exception：{ex}");
                                }
                            }
                            else
                            {
                                string menu = plugin1.PluginInfo.Type == 1 ? "菜单" : (plugin1.PluginInfo.Type == 2 ? "业务" : "数据源");
                                string message = Update(plugin1, pluginModel, plugin1.PluginInfo.Type) ? $"[{menu}]Success Update" : $"[{menu}]Failure Update";
                                _starryEdgeLog.LogRun(message);
                            }
                        }
                    }
                    else
                    {
                        plugin.UnLoadPlugin(_starryEdgeLog);
                    }
                }
            }
        }

        private void ExistsFiles(string filepath, Func<FileInfo, bool> func)
        {
            var directoryInfos = new DirectoryInfo(filepath);
            var files = directoryInfos.GetFiles().Where(func).ToArray();
            if (files.Length == 5)
            {
                var fc = new FileComparer();
                Array.Sort(files, fc);
                File.Delete(files[0].FullName);
            }
        }

        private bool Update(IPlugin plugin, IPlugin old_plugin, int type)
        {
            try
            {
                if (old_plugin != null)
                {
                    old_plugin.UnLoadPlugin(_starryEdgeLog);
                    string filename = Path.GetFileNameWithoutExtension(old_plugin.PluginInfo.DllPath).Split('_')[0];
                    ExistsFiles(_pluginBackup, x => Path.GetFileNameWithoutExtension(x.FullName).Contains(filename));
                    _compression.Compression(Path.Combine(_pluginBackup, Path.GetFileNameWithoutExtension(old_plugin.PluginInfo.DllPath) + $"_{DateTime.Now:yyMMddHHmmss}.7z"), old_plugin.PluginInfo.DllPath);
                    if (File.Exists(old_plugin.PluginInfo.DllPath))
                    {
                        File.Delete(old_plugin.PluginInfo.DllPath);
                    }
                    _plugins.Remove(old_plugin);
                }
                _plugins.Add(plugin);
                string barckupFile = Path.Combine(_pluginPluginCache, Path.GetFileName(plugin.PluginInfo.DllPath));
                if (File.Exists(plugin.PluginInfo.DllPath))
                {
                    File.Delete(plugin.PluginInfo.DllPath);
                }
                File.Move(barckupFile, plugin.PluginInfo.DllPath);
                File.Delete(barckupFile);
                plugin.LoadPlugin(_starryEdgeLog, plugin.PluginInfo.DllPath);
                if (type == 1)
                {
                    return _menuUpdate.UpdateMenu();
                }
                if (type == 2)
                {
                    _menuUpdate.UpdateParameter(2);
                    return _menuUpdate.UpdateHandle();
                }
                return _menuUpdate.UpdateParameter(1);
            }
            catch (Exception ex)
            {
                _starryEdgeLog.LogRun($"Plugin loading exception：{ex}", Core.Enums.LogLevel.Error);
            }
            return false;
        }

        public IPluginManager LoadDll()
        {
            if (!Directory.Exists(_pluginPath)) { Directory.CreateDirectory(_pluginPath); }
            DirectoryInfo directoryInfo = new DirectoryInfo(_pluginPath);
            var files = directoryInfo.GetFiles().Where(x => x.FullName.Contains("StarryEdge.Module")).ToArray();
            if (files.Length > 0)
            {
                lock (_lock)
                {
                    foreach (FileInfo fileInfo in files)
                    {
                        IPlugin plugin = new Plugin(new PluginInfoModel("", 1, 1, "1", "test", "test", "", ""));
                        Assembly assembly = plugin.LoadPlugin(_starryEdgeLog, fileInfo.FullName);
                        Type type = assembly.GetTypes().FirstOrDefault(t => t.IsClass && typeof(StarryEdge.Core.Models.PluginInfoModel).IsAssignableFrom(t));
                        if (type != null)
                        {
                            PluginInfoModel pluginInfo = Activator.CreateInstance(type) as StarryEdge.Core.Models.PluginInfoModel;
                            if (pluginInfo != null)
                            {
                                IPlugin plugin1 = new Plugin(new PluginInfoModel(pluginInfo.Name, pluginInfo.Id, pluginInfo.Type, pluginInfo.Version, pluginInfo.Author, pluginInfo.Description, fileInfo.FullName, pluginInfo.DllName));
                                plugin.UnLoadPlugin(_starryEdgeLog);
                                var pluginModel = _plugins.FirstOrDefault(x => x.PluginInfo.Id == pluginInfo.Id && x.PluginInfo.Type == pluginInfo.Type);
                                try
                                {
                                    _plugins.Add(plugin1);
                                    plugin1.LoadPlugin(_starryEdgeLog, plugin1.PluginInfo.DllPath);
                                }
                                catch (Exception ex)
                                {
                                    _starryEdgeLog.LogRun($"Plugin loading exception：{ex}", Core.Enums.LogLevel.Error);
                                }
                            }
                        }
                        else
                        {
                            plugin.UnLoadPlugin(_starryEdgeLog);
                        }
                    }
                    _menuUpdate.UpdateParameter(1);
                    _menuUpdate.UpdateParameter(2);
                    _menuUpdate.UpdateMenu();
                }
            }
            return this;
        }
    }
}
